#include "DistCaPValidator.hpp"

void * UserThreadAction(void *threadId)
{
	DistCaPValidator::TV_entry *myId;
	StandardPacket sp;
	clock_t t0,te;
	int run=1;

	myId = (DistCaPValidator::TV_entry*)threadId;
	IF_DBG_VERB(printf("[INFO] UserThreadAction: Thread %d avviato.\n",myId->id);)
	while(run)
	{
		pthread_mutex_lock(&(myId->U));
		//printf("Qui thread %d\n",myId->id);
		switch(myId->myStatus)
		{
			case DistCaPValidator::DIST_CAP_CS_RUN:
				//IF_DBG_VERB(printf("[INFO] UserThreadAction[%d]: Valutazione [%d,%d] \n",myId->id,myId->eval_from,myId->eval_from+myId->eval_len-1);)

				//Invio richiesta
				//IF_DBG_VERB(printf("[INFO] UserThreadAction[%d]: Send request...\n",myId->id);)

				sp.P_Type = PK_TYPE_EVAL;
				sp.payload.IntPayload = myId->eval_len;
				myId->proto->sendPacket(sp);
				//printf("ATTENDO CONFERMA...\n");
				//getchar();
				if(!myId->proto->waitConfirm())
				{
					IF_DBG_VERB(printf("[ERROR] UserThreadAction[%d]: NACK!\n",myId->id);)
				}
				else
				{
					//IF_DBG_VERB(printf("[INFO] UserThreadAction[%d]: ACK!\n",myId->id);)
					//IF_DBG_VERB(printf("[INFO] UserThreadAction[%d]: Invio popolazione...\n",myId->id);)

					t0 = clock();

					myId->Master->local_pop->save_to_file(myId->proto->my_socket, myId->eval_from,myId->eval_from+myId->eval_len-1, 1);
					//TODO: Gestire eccezione pop->save_to_file
					//IF_DBG_VERB(printf("[INFO] UserThreadAction[%d]: OK\n",myId->id);)
					if(!myId->proto->waitConfirm())
					{
						IF_DBG_VERB(printf("[ERROR] UserThreadAction[%d]: NACK!\n",myId->id);)
					}
					else
					{
						//IF_DBG_VERB(printf("[INFO] UserThreadAction[%d]: Attendo risultati...\n",myId->id);)
						//Lettura dei risultati
						myId->proto->getEvals(myId->Master->local_pop, myId->eval_from,myId->eval_from+myId->eval_len-1);
						te = clock();
						//TODO: gestire eccezioni da proto->getEvals
						//IF_DBG_VERB(printf("[INFO] UserThreadAction[%d]: Risultati OK\n",myId->id);)
						myId->proto->sendConfirm(1);

						myId->eval_time = (te-t0)/(float)(CLOCKS_PER_SEC);

					}
				}


				pthread_mutex_unlock(&(myId->M));
				break;
			case DistCaPValidator::DIST_CAP_CS_END:
				IF_DBG_VERB(printf("[INFO] UserThreadAction[%d]: Inizio Shutdown...\n",myId->id);)
				run = 0;
				if( myId->proto->startShutdown()) //TODO: Gestire eccezione startShutdown
					IF_DBG_VERB(printf("[INFO] UserThreadAction[%d]: Shutdown Completato\n",myId->id);)
				break;
		}
	}
	IF_DBG_VERB(printf("[INFO] UserThreadAction[%d]: Termino thread\n",myId->id);)
	pthread_exit(NULL);
}

DistCaPValidator::TV_entry *DistCaPValidator::StartConnection(string &IP,int PORT) throw(DistCaPValidatorException)
{
	int sck;
	struct sockaddr_in sin;
	TV_entry *tve;
	DistCaPProtocol *proto;

	sck = socket(AF_INET, SOCK_STREAM, 0);

	memset(&sin,0,sizeof(sockaddr_in));
	sin.sin_family = AF_INET;
	if(inet_pton( AF_INET, IP.c_str(), &sin.sin_addr )!=1)
		throw DistCaPValidatorException("DistCaPValidator::StartConnection: Errore nell'indirizzo del nodo");
	sin.sin_port = htons(PORT);

	//printf("Connessione...\n");
	if(connect(sck,(sockaddr*)&sin,sizeof(sin))==-1)
		throw DistCaPValidatorException("DistCaPValidator::StartConnection: Impossibile connettersi al nodo");

	proto = new DistCaPProtocol(sck);
	if(!proto->startHandshake(myValidator->n_eval,myValidator->simSteps,myValidator->minStartAngle,myValidator->maxStartAngle))
	{
		close(sck);
		delete proto;
		return NULL;
	}


	tve = (TV_entry*)malloc(sizeof(TV_entry));
	if(!tve)
		throw DistCaPValidatorException("DistCaPValidator::StartConnection: Impossibile allocare memoria per TV_entry");

	tve->userSocket = sck;
	tve->proto = proto;
	tve->evaluations = NULL;

	return tve;
}

void DistCaPValidator::StartThread(TV_entry *tve) throw(DistCaPValidatorException)
{
	if(pthread_mutex_init(&tve->M,NULL)!=0)
		throw DistCaPValidatorException("DistCaPValidator::StartThread: Impossibile creare Mutex");

	if(pthread_mutex_init(&tve->U,NULL)!=0)
		throw DistCaPValidatorException("DistCaPValidator::StartThread: Impossibile creare Mutex");

	if(pthread_mutex_lock(&tve->M)!=0)
	{
		pthread_mutex_destroy(&tve->M);
		pthread_mutex_destroy(&tve->U);
		throw DistCaPValidatorException("DistCaPValidator::StartThread: Impossibile lockare Mutex");
	}

	if(pthread_mutex_lock(&tve->U)!=0)
	{
		pthread_mutex_destroy(&tve->M);
		pthread_mutex_destroy(&tve->U);
		throw DistCaPValidatorException("DistCaPValidator::StartThread: Impossibile lockare Mutex");
	}

	if(pthread_create(&tve->thread_id, NULL, &UserThreadAction, (void *)tve)!=0)
	{
		pthread_mutex_destroy(&tve->M);
		pthread_mutex_destroy(&tve->U);
		throw DistCaPValidatorException("DistCaPValidator::StartThread: Impossibile creare il thread.");
	}

	tve->myStatus=DIST_CAP_CS_RUN;

	return;
}

DistCaPValidator::DistCaPValidator(GAmodSettings &settings,error_hand_type eht) throw(DistCaPValidatorException)
{
	const GAmodStrSetting *strsett;
    char *tmp;
	string IP_str,PORT_str,tmpstr;
	int PORT;
	TV_entry *tve;

	try
	{
		myValidator = new CaP_Validator(settings);
	}
	catch(EHW_Exception &e)
	{
		throw(e);
	}

	my_eval_time = 1;
	UserThreads.clear();
	strsett = (GAmodStrSetting*) settings.get_setting("nodes");
    tmp = (strsett) ? ((GAmodStrSetting*)strsett)->toStr() : NULL;
    if(!tmp)
        throw DistCaPValidatorException("DistCaPValidator::DistCaPValidator: impossibile trovare 'nodes'");

    tmpstr.assign(tmp);
    string::size_type loc,loc2;

    do{
        loc = tmpstr.find( ';', 0 );
        if(loc != string::npos)
        {
            PORT_str = tmpstr.substr(0,loc);
            tmpstr = tmpstr.substr(loc+1,tmpstr.size());

            //IF_DBG_VERB(printf("RM<%s>\n",tmpstr.c_str());)

            loc2 = PORT_str.find(':',0);
            if(loc2 != string::npos)
            {
                //printf("ID  = <%s>\n",tmp.c_str());
                IP_str = PORT_str.substr(0,loc2);
                PORT_str = PORT_str.substr(loc2+1,PORT_str.size());

				PORT = atoi(PORT_str.c_str());
				if(PORT<=0)
					throw DistCaPValidatorException("DistCaPValidator::DistCaPValidator: numero di PORT non valido");

				IF_DBG_VERB(printf("[INFO] DistCaPValidator::DistCaPValidator: Calling node %s:%d...\n",IP_str.c_str(),PORT);)


				try {
					if((tve=StartConnection(IP_str,PORT))==NULL)
					{
						IF_DBG_VERB(printf("[INFO] DistCaPValidator::DistCaPValidator: Handshake fallito\n");)
					}
					else
					{
						IF_DBG_VERB(printf("[INFO] DistCaPValidator::DistCaPValidator: Handshake OK\n");)
						IF_DBG_VERB(printf("[INFO] DistCaPValidator::DistCaPValidator: Avvio thread utente...\n");)
						tve->id = UserThreads.size();

						try {
							StartThread(tve);
							IF_DBG_VERB(printf("[INFO] DistCaPValidator::DistCaPValidator: Thread utente avviato\n");)
							tve->eval_time = 1;
							tve->evaluations = NULL;
							tve->Master = this;
							UserThreads.push_back(tve);
						}
						catch (DistCaPValidatorException &e) {
							std::string errmsg("DistCaPValidator::DistCaPValidator:\n\t");
							errmsg = errmsg + e.msg;
							throw DistCaPValidatorException((char*)errmsg.c_str());
						}
					}
				}
				catch (DistCaPValidatorException &e)
				{
					IF_DBG_INFO(printf("[ERROR] DistCaPValidator::DistCaPValidator: Impossibile contattare host.\n\t%s\n",e.msg.c_str());)
				}
			}
            else
                throw DistCaPValidatorException("DistCaPValidator::DistCaPValidator: parametro 'nodes' malformato");
        }
    }while(loc!=string::npos);

	IF_DBG_VERB(printf("[INFO] DistCaPValidator::DistCaPValidator: Contattati %d nodi.\n",UserThreads.size());)

	my_eval_time = 1;
	//getchar();
}

unsigned int DistCaPValidator::valuta_pop(Popolazione *pop,stat_generation sg) throw(DistCaPValidatorException)
{
	int p_size,t_len,i;
	vector<TV_entry*>::iterator it;
	clock_t t0,te;

	p_size = pop->get_size();

	local_pop = pop;
	//Divisione equa delle valutazioni
	t_len = p_size / (UserThreads.size() + 1);
	my_eval_len = t_len;
	//pop->print_all();

	//printf("Local val[0-%d]\n",t_len-1);
	//printf("[%d]\n",UserThreads.size());

	//pop->print_all();

	for(it=UserThreads.begin(), i=0; it!=UserThreads.end(); it++,i++)
	{
		//printf("Rem[%d] [%d-%d]\n",i,(i+1)*t_len,(i+2)*t_len-1);
		(*it)->eval_from = (i+1)*t_len;
		(*it)->eval_len = t_len;
		if(p_size-(i+1)*t_len< 2*t_len)
				(*it)->eval_len = p_size-(i+1)*t_len;
		pthread_mutex_unlock(&(*it)->U);
	}

	//Valutazione locale...
	t0 = clock();
	myValidator->valuta_pop(pop,0,t_len-1);
	te = clock();

	my_eval_time = (te-t0)/(float)CLOCKS_PER_SEC;

	i=0;
	for(it=UserThreads.begin(); it!=UserThreads.end(); it++,i++)
	{
		pthread_mutex_lock(&(*it)->M);
		//printf("unlock mutex %d\n",i++);
		//printf("%d@[%.2f]\n",i,(*it)->eval_time);
	}

	te = clock();
	printf("[INFO] DistCaPValidator::valuta_pop: EvT:[%f]\n",(te-t0)/(float)(CLOCKS_PER_SEC));

	/*printf("[%.2f]",my_eval_time);
	for(it=UserThreads.begin(); it!=UserThreads.end(); it++)
	{
		printf("[%.2f]",(*it)->eval_time);
	}
	printf("\n");*/
	//update_load_distribution(p_size);
	//getchar();
	//printf("FINE DELLA VALUTAZIONE\n");
	//getchar();

	//TODO: Ritornare bestScore
	return 0;
}

unsigned int DistCaPValidator::snapIndividuo(Individuo &Ind,char *path) throw(DistCaPValidatorException)
{
	try
	{
		return myValidator->snapIndividuo(Ind, path);
	}
	catch(CaP_ValidatorException &e)
	{
		std::string errmsg("DistCaPValidator::snapIndividuo:\n\t");
		errmsg = errmsg + e.msg;
		throw DistCaPValidatorException((char*)errmsg.c_str());
	}
}

void DistCaPValidator::update_load_distribution(int p_size)
{
	float tot_speed;
	vector<TV_entry*>::iterator it;
	int i;

	my_eval_per_sec = my_eval_len/my_eval_time;
	tot_speed = my_eval_per_sec;

	printf("S0: %.2f\n",my_eval_per_sec);

	i=1;
	for(it=UserThreads.begin(); it!=UserThreads.end(); it++)
	{
		(*it)->eval_per_sec = (*it)->eval_len / (*it)->eval_time;
		printf("S%d: %.2f\n",i++,(*it)->eval_per_sec);
		tot_speed += (*it)->eval_per_sec;
	}

	printf("L0: %.2f -> %.0f\n",my_eval_per_sec/tot_speed,ceil(p_size*my_eval_per_sec/tot_speed));

	i=1;
	for(it=UserThreads.begin(); it!=UserThreads.end(); it++)
		printf("L%d: %.2f -> %.0f\n",i++,(*it)->eval_per_sec/tot_speed,ceil(p_size*(*it)->eval_per_sec/tot_speed));

}

DistCaPValidator::~DistCaPValidator()
{
	delete myValidator;
	IF_DBG_INFO(printf("[INFO]DistCaPValidator::~DistCaPValidator: Avvio Shutdown...\n");)
	vector<TV_entry*>::iterator it;

	for(it=UserThreads.begin(); it!=UserThreads.end(); it++)
	{
		(*it)->myStatus = DIST_CAP_CS_END;
		pthread_mutex_unlock(&(*it)->U);
	}
	pthread_exit(NULL);
}
